package gg.zsw.dataframework.pubsub;

import lombok.extern.slf4j.Slf4j;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


/**
 * 采用推的方式进行通知
 * 动态的注册
 *
 * @param <Observer>
 * @param <NotifyData>
 * @author zsw
 * @date 2019/9/16 16:05
 */
@Slf4j
public abstract class AbstractSubject<Observer extends AbstractObserver<NotifyData>, NotifyData> {

    /**
     *
     */
    protected volatile Map<String, Observer> observers = new ConcurrentHashMap<>();

    /**
     * 注意: 可以设计成注册的时候立即推送最新数据
     * 注意: 加了synchronized，线程安全的，但是没有针对相同key做处理
     *
     * @param key
     * @param o
     */
    public synchronized void registerObserver(String key, Observer o) {
        observers.put(key, o);
        log.info(this.toString() + "|注册：" + o.toString() + "|size:" + observers.size());
    }


    /**
     * @param <Observer>
     */
    @FunctionalInterface
    public interface CreateObserver<Observer> {
        Observer create(String key);
    }

    /**
     * 经常用到，获取指定的观察者，如果当前这个观察者还没注册，就生成并注册进去
     * 注意: 由于removeObserver可能通过调用nonObserverCallBack产生一些意想不到的状况，所以为了安全起见，加上synchronize。
     *
     * @param key
     * @param fun
     * @return
     */
    public synchronized Observer getObserverAutoRegisterIfAbsent(String key, CreateObserver<Observer> fun) {
        Observer observer = observers.get(key);
        if (observer != null) {
            return observer;
        } else {
            Observer o = fun.create(key);
            Observer obs = observers.putIfAbsent(key, o);
            if (obs != null) {
                return obs;
            } else {
                return o;
            }
        }


    }

    public synchronized void removeObserver(String key) {
        Observer o = observers.remove(key);
        //todo observers被移光后，可能会让主题本身也消失。
        if (observers.isEmpty()) {
            nonObserverCallBack();
        }
    }

    /**
     * 当这个主题，没有任何一个观察者时，它要做什么事情？
     * 销毁自己？还是继续保留，等待下一个观察者的到来？
     * 默认空实现，继续存在，等待观察者
     */
    public void nonObserverCallBack() {

    }


    /**
     * 发出消息通知观察者
     * 自定义的通知方法，如果默认实现满足的话，不用这个也行
     * zsw: 本人暂未遇到需要自定义的情况，两种默认实现加上外部一些处理，已经满足大量场景了。太复杂的话分层处理更好
     *
     * @param data
     */
    protected abstract void notify(NotifyData data);


    /**
     * 全量通知实现
     *
     * @param data
     */
    protected void notifyAll(NotifyData data) {
        beforeNotify(data);
        observers.values().forEach(observer -> {
            observer.update(data);
        });
    }


    /**
     * 指定相应的key进行通知
     * 发布订阅模式变成了分类分发器
     *
     * @param key
     * @param data
     */
    protected void notifyKey(String key, NotifyData data) {
        beforeNotify(data);
        Observer observer = observers.get(key);
        if (observer != null) {
            observer.update(data);
        }
    }

    /**
     * 例如：可以通过这个接口做点打印什么的
     *
     * @param data
     */
    protected void beforeNotify(NotifyData data) {

    }

}
